package com.javasea.log4j2.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.regex.Pattern;

/**
 * ip地址转换和获取工具类
 *
 * @author: 龙小南
 * @Date: 2023/2/20 17:09
 * @Version 1.0[当前版本号]
 *
 * @version 1.0 2023/2/20 17:09 龙小南 新建类[说明]
 */
public class IPUtils {

    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
    /**
     * 本地局域网IP
     */
    public static final String LOCAL_IPADDR = getLocalIntAddress();

    /**
     * 默认IPV4
     */
    private static final String LOCAL_IPV4 = "127.0.0.1";
    /**
     * 默认IPV6
     */
    private static final String LOCAL_IPV6 = "::1";
    /**
     * 功能：判断IPv4地址的正则表达式：
     */
    private static final Pattern IPV4_REGEX = Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");

    /**
     * 功能：判断标准IPv6地址的正则表达式
     */
    private static final Pattern IPV6_STD_REGEX = Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");

    /**
     * 功能：判断一般情况压缩的IPv6正则表达式
     */
    private static final Pattern IPV6_COMPRESS_REGEX = Pattern.compile("^((?:[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})*)?)::((?:([0-9A-Fa-f]{1,4}:)*[0-9A-Fa-f]{1,4})?)$");

    /**
     * 由于IPv6压缩规则是必须要大于等于2个全0块才能压缩
     * 不合法压缩 ： fe80:0000:8030:49ec:1fc6:57fa:ab52:fe69
     * ->           fe80::8030:49ec:1fc6:57fa:ab52:fe69
     * 该不合法压缩地址直接压缩了处于第二个块的单独的一个全0块，
     * 上述不合法地址不能通过一般情况的压缩正则表达式IPV6_COMPRESS_REGEX判断出其不合法
     * 所以定义了如下专用于判断边界特殊压缩的正则表达式
     * (边界特殊压缩：开头或末尾为两个全0块，该压缩由于处于边界，且只压缩了2个全0块，不会导致':'数量变少)
     *
     * 功能：抽取特殊的边界压缩情况
     */
    private static final Pattern IPV6_COMPRESS_REGEX_BORDER = Pattern.compile("^(::(?:[0-9A-Fa-f]{1,4})(?::[0-9A-Fa-f]{1,4}){5})|((?:[0-9A-Fa-f]{1,4})(?::[0-9A-Fa-f]{1,4}){5}::)$");


    private IPUtils() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 获取上一个节点的IP地址
     * !!要注，如果上一个节点是Nginx或Apache做代理转发，调用该函数会获取Nginx或Apache的IP地址，如果实现获取代理服务器之前节点的，请使用getIpAddr
     */
    public static String getRemoteIpAddr(HttpServletRequest request)
    {
    	if(null == request) {
    		return null;
    	}

        String remoteAddr = request.getRemoteAddr();
    	String realIp = request.getHeader("X-Real-IP");
    	String forwardIp = request.getHeader("x-forwarded-for");
    	logger.info("RemoteAddr={}, X-Real-IP={}, X-Forwarded-For={}", remoteAddr, realIp, forwardIp);

    	return remoteAddr;
    }

    /**
     * 该函数适用于通过Nginx或Apache代理转发到后端服务器的情景，如果是直接访问后端服务器的，请使用getRemoteIpAddr
     *
     * 获取nginx的RemoteIp获取到X-Real-IP或request.getRemoteAddr()的IP地址，该方法获取到的IP地址一般不会被伪造
     * ！！注意如果是不通过nginx的，则直接使用request.getRemoteAddr()，不要调用该类及函数获取！！
     *
     ** 1、使用Nginx反向代理服务器，且是单层反向代理，
     ** Nginx配置如下：
     *   proxy_set_header X-Real-IP $remote_addr;
     *
     ** 2、使用Nginx反向代理服务器，且有多层反向代理：
     ** 最外层Nginx配置如下：
     *    proxy_set_header X-Real-IP $remote_addr;
     *  非最外层Nginx配置如下：
     *    proxy_set_header X-Real-IP $http_x_real_ip;
     *
     ** 3、获取客户端IP的代码如下：
     *  String realIp = request.getHeader("X-Real-IP");
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request, boolean printLog)
    {
    	if(null == request) {
    		return null;
    	}

        String remoteAddr = request.getRemoteAddr();
    	String realIp = request.getHeader("X-Real-IP");
    	String forwardIp = request.getHeader("x-forwarded-for");
    	if(printLog) {
    		logger.info("RemoteAddr={}, X-Real-IP={}, X-Forwarded-For={}", remoteAddr, realIp, forwardIp);
    	}

    	//先检测X-Real-IP是否外网地址，是则直接返回
        try {
        	if(isInterIp(realIp)) {
                return realIp;
            }

	        //检测remoteAddr是否外网地址，是则直接返回
	    	if(isInterIp(remoteAddr)) {
	            return remoteAddr;
	        }

        } catch(Exception e) {
        	logger.error("分析X-Real-IP异常", e);
        }
    	//如果realIp为空，取remoteAddr
    	if(null == realIp || 0 == realIp.trim().length()) {
    		realIp = remoteAddr;
    	}

    	return realIp;
    }

    /**
     * 该函数适用于通过Nginx或Apache代理转发到后端服务器的情景，如果是直接访问后端服务器的，请使用getRemoteIpAddr
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request)
    {
    	return getIpAddr(request, false);
    }


    /**
     * 检测是否互联网IP
     *
     * @param ip
     * @return
     */
    public static boolean isInterIp(String ip) {
        if(null == ip  || ip.trim().length() == 0 || "unknown".equalsIgnoreCase(ip.trim())) {
            return false;
        }

        ip = ip.trim();

        //存在冒号“:”的都算IPv6，且不以“FE80::”"FC00::"开头的为外网地址
        if(ip.indexOf(":") >= 0
        	&& ip.length() > 6
        	&& !ip.toUpperCase().startsWith("FE80::")
        	&& !ip.toUpperCase().startsWith("FC00::")
          )
        {
            return true;
        }


        //判断127.x.x.x/8, 10.x.x.x/8, 172.16.x.x/12, 192.168.x.x/16
        byte[] addr = textToNumericFormatV4(ip);
        if(addr.length<2) {
        	return false;
        }

        final byte b0 = addr[0];
        final byte b1 = addr[1];
        //127.x.x.x/8
        final byte SECTION_0 = 0x7F;
        //10.x.x.x/8
        final byte SECTION_1 = 0x0A;
        //172.16.x.x/12
        final byte SECTION_2 = (byte) 0xAC;
        final byte SECTION_3 = (byte) 0x10;
        final byte SECTION_4 = (byte) 0x1F;
        //192.168.x.x/16
        final byte SECTION_5 = (byte) 0xC0;
        final byte SECTION_6 = (byte) 0xA8;

        switch (b0) {
            case SECTION_0: {
                return false;
            }
            case SECTION_1: {
                return false;
            }
            case SECTION_2: {
            	return !(b1 >= SECTION_3 && b1 <= SECTION_4) ;
            }
            case SECTION_5: {
            	return b1 != SECTION_6;
            }
            default: {
                return true;
            }
        }

    }


    /**
     * 将IPv4地址转为HEX
     * @param ipAddr
     * @return
     */
    public static byte[] textToNumericFormatV4(String ipAddr) {
		if (ipAddr.length() == 0) {
			return new byte[0];
		} else {
			byte[] var1 = new byte[4];
			String[] var2 = ipAddr.split("\\.", -1);

			try {
				long var3;
				int var5;
				switch (var2.length) {
					case 1 :
						var3 = Long.parseLong(var2[0]);
						if (var3 < 0L || var3 > 4294967295L) {
							return new byte[0];
						}

						var1[0] = (byte) ((int) (var3 >> 24 & 255L));
						var1[1] = (byte) ((int) ((var3 & 16777215L) >> 16 & 255L));
						var1[2] = (byte) ((int) ((var3 & 65535L) >> 8 & 255L));
						var1[3] = (byte) ((int) (var3 & 255L));
						break;
					case 2 :
						var3 = Long.parseLong(var2[0]);
						if (var3 >= 0L && var3 <= 255L) {
							var1[0] = (byte) ((int) (var3 & 255L));
							var3 = Long.parseLong(var2[1]);
							if (var3 < 0L || var3 > 16777215L) {
								return new byte[0];
							}

							var1[1] = (byte) ((int) (var3 >> 16 & 255L));
							var1[2] = (byte) ((int) ((var3 & 65535L) >> 8 & 255L));
							var1[3] = (byte) ((int) (var3 & 255L));
							break;
						}

						return new byte[0];
					case 3 :
						for (var5 = 0; var5 < 2; ++var5) {
							var3 = Long.parseLong(var2[var5]);
							if (var3 < 0L || var3 > 255L) {
								return new byte[0];
							}

							var1[var5] = (byte) ((int) (var3 & 255L));
						}

						var3 = Long.parseLong(var2[2]);
						if (var3 < 0L || var3 > 65535L) {
							return new byte[0];
						}

						var1[2] = (byte) ((int) (var3 >> 8 & 255L));
						var1[3] = (byte) ((int) (var3 & 255L));
						break;
					case 4 :
						for (var5 = 0; var5 < 4; ++var5) {
							var3 = Long.parseLong(var2[var5]);
							if (var3 < 0L || var3 > 255L) {
								return new byte[0];
							}

							var1[var5] = (byte) ((int) (var3 & 255L));
						}

						return var1;
					default :
						return new byte[0];
				}

				return var1;
			} catch (NumberFormatException var6) {
				return new byte[0];
			}
		}
	}


    /**
     * 获取IP地址, 优先取x-forwarded-for，由于x-forwarded-for容易被伪造，非特殊场景不建议使用该函数
     * <p>
     * 使用Nginx等反向代理软件， 则不能通过request.getRemoteAddr()获取IP地址
     * 如果使用了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP地址，X-Forwarded-For中第一个非unknown的有效IP字符串，则为真实IP地址
     */
    public static String getXforwardedIpAddr(HttpServletRequest request)
    {
    	if(null == request) {
    		return null;
    	}

        String ipAddress = LOCAL_IPV4;
        try {
            String remoteAddr = request.getRemoteAddr();
        	String realIp = request.getHeader("X-Real-IP");
        	String forwardIp = request.getHeader("x-forwarded-for");

        	logger.info("X-Real-IP={}, remoteAddr={}, X-Forwarded-For={}", realIp, remoteAddr, forwardIp);


            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (LOCAL_IPV4.equals(ipAddress)) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                    	logger.error("获取IP异常: {}",e.getMessage());
                    }
                    if(inet!=null){
                    	ipAddress = inet.getHostAddress();
                    }

                }
            }
            // 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15 && ipAddress.indexOf(',') > -1) { // "***.***.***.***".length()
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(','));
            }
        } catch (Exception e) {
        	logger.error("获取IP异常",e);
            ipAddress = LOCAL_IPV4;
        }

        return LOCAL_IPV6.equals(ipAddress) ? LOCAL_IPV4 : ipAddress;
    }


    /**
     * 获取本地IP地址
     * 受限于主机名指定的ip，如果更改hosts文件主机名指向127.0.0.1，则获取到的IP为127.0.0.1
     * @return
     */
    public static String getLocalIpAddr() {
        try {
            InetAddress address = InetAddress.getLocalHost();
            if (address != null) {
            	String ip = address.getHostAddress();

            	if("127.0.0.1".equals(ip) || "::1".equals(ip)){
            		ip = getLocalIntAddress();
            	}

                return ip;
            }
        } catch (UnknownHostException e) {
            logger.error("获取本地IP地址失败",e);
        }
        return null;
    }


    /**
     * 获得本地服务器的对外IP地址
     *
     * @return 对外IP地址
     */
    public static String getLocalIntAddress() {
        try {
            Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
            InetAddress inetAddress = null;
            Enumeration<InetAddress> addrs;
            String intAddress;
            NetworkInterface networkInterface;
            while (networks.hasMoreElements()) {
                networkInterface = networks.nextElement();
                if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) {
                    continue;
                }

                addrs = networkInterface.getInetAddresses();
                while (addrs.hasMoreElements()) {
                    inetAddress = addrs.nextElement();

                    if (inetAddress instanceof Inet4Address) {
                        intAddress = inetAddress.getHostAddress();
                        return intAddress;
                    }
                }
            }

            return getLocalIpAddr();
        } catch (Exception e) {
            logger.error("获取网络IP失败", e);
            return LOCAL_IPV4;
        }

    }


    /**
     * 判断是否为合法IPv4地址
     * @param input ip地址
     * @return
     */
    public static boolean isIPv4Address(final String input){
        return IPV4_REGEX.matcher(input).matches();
    }

    /**
     * 判断是否为合法IPv6地址
     * @param input ip地址
     * @return
     */
    public static boolean isIPv6Address(final String input) {
        int num = 0;
        for(int i = 0;i<input.length();i++){
            if(input.charAt(i) == ':') {
            	num++;
            }
        }
        if(num > 7) {
            return false;
        }
        if(IPV6_STD_REGEX.matcher(input).matches()){
            return true;
        }
        if(num == 7){
            return IPV6_COMPRESS_REGEX_BORDER.matcher(input).matches();
        }
        else{
            return  IPV6_COMPRESS_REGEX.matcher(input).matches();
        }
    }

}
